#region License and Copyright

/*
 * Copyright (C) 2008 Nathan Palmer
 * http://www.nathanpalmer.com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * This code was written with help from the forums and examples at the
 * Amazon Web Services Developer Connection website. 
 * 
 * Author:
 *     Nathan Palmer <nathan1@nathanpalmer.com>
 */

#endregion

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Web;
using System.Web.Services.Protocols;
using Amazon.S3.com.amazonaws.s3;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Referral;
using Microsoft.Web.Services2.Policy;
using Microsoft.Web.Services2.Dime;


namespace Amazon
{
    internal class S3Helper
    {
        private const string ISODateFormat = "yyyy-MM-ddTHH:mm:ss.fffZ";
        private const string AmazonS3 = "AmazonS3";

        private string accessKeyId;
        private string secretAccessKey;
        private AmazonS3 s3WebService = new AmazonS3();

        public S3Helper(string AccessKeyId, string SecretKey)
        {
            accessKeyId = AccessKeyId;
            secretAccessKey = SecretKey;

            s3WebService.Pipeline.OutputFilters.Remove(typeof(SecurityOutputFilter));
            s3WebService.Pipeline.OutputFilters.Remove(typeof(ReferralOutputFilter));
            s3WebService.Pipeline.OutputFilters.Remove(typeof(PolicyEnforcementOutputFilter));
            s3WebService.Pipeline.OutputFilters.Add(new Amazon.HeaderOutputFilter("wsa:"));
        }

        #region "Private Class Methods"

        private DateTime AWSGetDateStamp()
        {
            DateTime CurrentDateTime;
            DateTime FriendlyDateTime;

            CurrentDateTime = DateTime.Now;

            FriendlyDateTime = new DateTime(
                CurrentDateTime.Year,
                CurrentDateTime.Month,
                CurrentDateTime.Day,
                CurrentDateTime.Hour,
                CurrentDateTime.Minute,
                CurrentDateTime.Second,
                CurrentDateTime.Millisecond,
                DateTimeKind.Local);

            return FriendlyDateTime;
        }

        private string AWSGetISOtimeStamp(DateTime TimeStamp)
        {
            string isoTimeStamp;

            isoTimeStamp = TimeStamp.ToUniversalTime().ToString(ISODateFormat, System.Globalization.CultureInfo.InvariantCulture);

            return isoTimeStamp;
        }

        private string AWSGetSignature(string operation, DateTime timeStamp)
        {
            string sigRaw;
            string signature;

            UTF8Encoding utf8Encoder;
            HMACSHA1 hmaCSHA1;

            sigRaw = AmazonS3 + operation + AWSGetISOtimeStamp(timeStamp);
            utf8Encoder = new UTF8Encoding();
            hmaCSHA1 = new HMACSHA1(utf8Encoder.GetBytes(secretAccessKey));
            signature = Convert.ToBase64String(hmaCSHA1.ComputeHash(utf8Encoder.GetBytes(sigRaw.ToCharArray())));

            return signature;
        }

        private bool AreMaxKeysSpecified(Int32 KeyCount)
        {
            if (KeyCount > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region "Bucket Methods"

        public bool CreateBucket(string BucketName, out SoapResponse<CreateBucketResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;
            CreateBucketResult createBucketResult;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("CreateBucket", timeStamp);

            try
            {
                createBucketResult = s3WebService.CreateBucket(BucketName, null, accessKeyId, timeStamp, true, signature);
                SoapResponse = new SoapResponse<CreateBucketResult>(createBucketResult);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<CreateBucketResult>(se.Message.ToString());
                createBucketResult = null;

                return false;
            }
        }

        public bool DeleteBucket(string BucketName, out SoapResponse<Status> SoapRrror)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("DeleteBucket", timeStamp);

            try
            {
                Status result = s3WebService.DeleteBucket(BucketName, accessKeyId, timeStamp, true, signature, null);
                SoapRrror = new SoapResponse<Status>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapRrror = new SoapResponse<Status>(se);

                return false;
            }
        }

        public bool ListAllMyBuckets(out SoapResponse<ListAllMyBucketsResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("ListAllMyBuckets", timeStamp);

            try
            {
                ListAllMyBucketsResult result = s3WebService.ListAllMyBuckets(accessKeyId, timeStamp, true, signature);
                SoapResponse = new SoapResponse<ListAllMyBucketsResult>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<ListAllMyBucketsResult>(se.Message.ToString());

                return false;
            }
        }

        public bool ListBucket(string BucketName, string Prefix, string Marker, string Delimiter, Int32 MaxKeys, out SoapResponse<ListBucketResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("ListBucket", timeStamp);

            try
            {
                ListBucketResult result = s3WebService.ListBucket(BucketName, Prefix, Marker, MaxKeys, AreMaxKeysSpecified(MaxKeys), Delimiter, accessKeyId, timeStamp, true, signature, null);
                SoapResponse = new SoapResponse<ListBucketResult>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<ListBucketResult>(se);

                return false;
            }
        }

        #endregion

        #region "Object Methods"

        public bool PutObject(string BucketName, string KeyName, string ContentType, long InputLength, System.IO.Stream InputStream, MetadataEntry[] MetaData, Grant[] AccessControlList, out SoapResponse<PutObjectResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("PutObject", timeStamp);

            try
            {
                DimeAttachment dimeAttachment = new Microsoft.Web.Services2.Dime.DimeAttachment(ContentType, Microsoft.Web.Services2.Dime.TypeFormat.Unknown, InputStream);
                s3WebService.RequestSoapContext.Attachments.Add(dimeAttachment);

                PutObjectResult result = s3WebService.PutObject(BucketName, KeyName, MetaData, InputLength, AccessControlList, StorageClass.STANDARD, false, accessKeyId, timeStamp, true, signature, null);
                SoapResponse = new SoapResponse<PutObjectResult>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<PutObjectResult>(se);
                return false;
            }
        }

        public bool GetObject(string BucketName, string KeyName, ref byte[] Data, out SoapResponse<GetObjectResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("GetObject", timeStamp);

            try
            {
                GetObjectResult result = s3WebService.GetObject(BucketName, KeyName, true, true, false, accessKeyId, timeStamp, true, signature, null);

                if (s3WebService.ResponseSoapContext != null)
                {
                    if (s3WebService.ResponseSoapContext.Attachments != null)
                    {
                        int DataLength = (int)s3WebService.ResponseSoapContext.Attachments[0].Stream.Length;
                        Data = new byte[DataLength];
                        s3WebService.ResponseSoapContext.Attachments[0].Stream.Read(Data, 0, DataLength);
                    }
                }

                SoapResponse = new SoapResponse<GetObjectResult>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<GetObjectResult>(se);
                return false;
            }
        }

        public bool GetObjectExtended(string BucketName, string KeyName, long ByteRangeStart, long ByteRangeEnd, ref byte[] Data, out SoapResponse<GetObjectResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("GetObjectExtended", timeStamp);

            try
            {
                GetObjectResult result = s3WebService.GetObjectExtended(BucketName, KeyName, true, true, false, ByteRangeStart, true, ByteRangeEnd, true, DateTime.Now, false, DateTime.Now, false, null, null, false, false, accessKeyId, timeStamp, true, signature, null);

                if (s3WebService.ResponseSoapContext != null)
                {
                    if (s3WebService.ResponseSoapContext.Attachments != null)
                    {
                        int DataLength = (int)s3WebService.ResponseSoapContext.Attachments[0].Stream.Length;
                        Data = new byte[DataLength];
                        s3WebService.ResponseSoapContext.Attachments[0].Stream.Read(Data, 0, DataLength);
                    }
                }

                SoapResponse = new SoapResponse<GetObjectResult>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<GetObjectResult>(se);
                return false;
            }
        }

        public bool GetObjectMetadata(string BucketName, string KeyName, out SoapResponse<GetObjectResult> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("GetObject", timeStamp);

            try
            {
                GetObjectResult result = s3WebService.GetObject(BucketName, KeyName, true, false, false, accessKeyId, timeStamp, true, signature, null);
                
                SoapResponse = new SoapResponse<GetObjectResult>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<GetObjectResult>(se);
                return false;
            }
        }


        public bool DeleteObject(string BucketName, string KeyName, out SoapResponse<Status> SoapResponse)
        {
            DateTime timeStamp;
            string signature;

            timeStamp = AWSGetDateStamp();
            signature = AWSGetSignature("DeleteObject", timeStamp);

            try
            {
                Status result = s3WebService.DeleteObject(BucketName, KeyName, accessKeyId, timeStamp, true, signature, null);
                SoapResponse = new SoapResponse<Status>(result);

                return true;
            }
            catch (SoapException se)
            {
                SoapResponse = new SoapResponse<Status>(se);
                return false;
            }
            catch (Exception ex)
            {
                SoapResponse = new SoapResponse<Status>(ex);
                return false;
            }
        }

        #endregion

        public string GetContentType(string FileExtension)
        {
            Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.ClassesRoot;
            Microsoft.Win32.RegistryKey subKey = key.OpenSubKey(FileExtension);

            return subKey.GetValue("Content Type").ToString();
        }
    }

    internal class SoapResponse<T>
    {
        private bool isError;
        private string errorMessage;
        private T state;

        internal SoapResponse(string ErrorMessage)
        {
            isError = true;
            errorMessage = ErrorMessage;
        }

        internal SoapResponse(T State)
        {
            isError = false;
            errorMessage = "";
            state = State;
        }

        internal SoapResponse(SoapException Exception)
        {
            isError = true;
            errorMessage = Exception.Message;
            state = State;
            //code = Exception.Code;            
        }

        internal SoapResponse(Exception Exception)
        {
            isError = true;
            errorMessage = Exception.Message;
            state = State;
        }
        
        public bool Error
        {
            get
            {
                return isError;
            }
        }

        public string ErrorMessage
        {
            get
            {
                return errorMessage;
            }
        }

        public T State
        {
            get
            {
                return state;
            }
        }
    }
}
